// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-24

package src

import (
	"context"
	"fmt"
	"gitee.com/go-libs/config"
	"gitee.com/go-libs/crontab"
	"gitee.com/go-libs/log"
	_ "gitee.com/go-libs/validator-iris/validator"
	"gitee.com/go-wares/framework-iris/framework/src/conf"
	"gitee.com/go-wares/framework-iris/framework/src/middlewares"
	"github.com/kataras/iris/v12"
	"github.com/kataras/iris/v12/middleware/pprof"
	"github.com/kataras/iris/v12/mvc"
	"reflect"
	"strings"
	"sync/atomic"
	"time"
)

// Application
// is a component for iris web framework.
type Application struct {
	cancel context.CancelFunc
	ctx    context.Context

	annotationControllers []*ControllerEntry
	annotationCrontabs    []*crontab.Worker
	annotationHandlers    []*HandlerEntry
	annotationMiddlewares map[string]iris.Handler

	cron                     crontab.Cron
	framework                *iris.Application
	middlewares              []iris.Handler
	parallelRunning, running int32
	parallelRunners          []ParallelRunner
	rule                     iris.ExecutionRules
}

// AddAnnotationControllers
// adds a controller entry list into application.
func (o *Application) AddAnnotationControllers(entries ...*ControllerEntry) *Application {
	var list = make([]*ControllerEntry, 0)

	// Range
	// given controllers.
	for _, entry := range entries {
		if entry == nil || entry.Controller == nil {
			continue
		}
		list = append(list, entry)
	}

	// Add to
	// controller list if necessary.
	if len(list) > 0 {
		o.annotationControllers = append(o.annotationControllers, list...)
	}
	return o
}

// AddAnnotationCrontabs
// adds a crontab worker list into application.
func (o *Application) AddAnnotationCrontabs(workers ...*crontab.Worker) *Application {
	var list = make([]*crontab.Worker, 0)

	// Range
	// crontabs with given list in parameter.
	for _, worker := range workers {
		if worker == nil {
			continue
		}
		list = append(list, worker)
	}

	// Add to
	// crontab list if necessary.
	if len(list) > 0 {
		o.annotationCrontabs = append(o.annotationCrontabs, list...)
	}
	return o
}

// AddAnnotationHandlers
// adds an annotation handlers list for application.
func (o *Application) AddAnnotationHandlers(entries ...*HandlerEntry) *Application {
	var list = make([]*HandlerEntry, 0)

	// Range
	// given handlers list.
	for _, v := range entries {
		if v == nil {
			continue
		}

		// Request method
		// convert to upper-case letter.
		if v.Method != "" {
			v.Method = strings.ToUpper(v.Method)
		} else {
			v.Method = "ANY"
		}

		list = append(list, v)
	}

	// Add to
	// handler list if necessary.
	if len(list) > 0 {
		o.annotationHandlers = append(o.annotationHandlers, list...)
	}
	return o
}

// AddAnnotationMiddlewares
// adds an annotation middlewares mapping for application.
func (o *Application) AddAnnotationMiddlewares(mapper map[string]iris.Handler) *Application {
	// Range
	// given middleware mapping.
	for name, handler := range mapper {
		// Remove
		// handler by name from global mapping.
		if handler == nil {
			if _, ok := o.annotationMiddlewares[name]; ok {
				delete(o.annotationMiddlewares, name)
			}
			continue
		}

		// Update
		// global middlewares mapping.
		o.annotationMiddlewares[name] = handler
	}
	return o
}

// AddMiddlewares
// adds a global middleware list into application. All controllers and handlers
// are affected.
func (o *Application) AddMiddlewares(middlewares ...iris.Handler) {
	for _, x := range middlewares {
		if x != nil {
			o.middlewares = append(o.middlewares, x)
		}
	}
}

// AddParallels
// adds a runner list into application.
func (o *Application) AddParallels(runners ...ParallelRunner) *Application {
	var list = make([]ParallelRunner, 0)

	// Range
	// given runners list.
	for _, runner := range runners {
		if runner == nil {
			continue
		}
		list = append(list, runner)
	}

	// Add to
	// parallel runner list if necessary.
	if len(list) > 0 {
		o.parallelRunners = append(o.parallelRunners, list...)
	}
	return o
}

// Framework
// returns an iris web framework application.
func (o *Application) Framework() *iris.Application {
	return o.framework
}

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

// Init
// prepare application required fields.
func (o *Application) init() *Application {
	o.annotationControllers = make([]*ControllerEntry, 0)
	o.annotationCrontabs = make([]*crontab.Worker, 0)
	o.annotationHandlers = make([]*HandlerEntry, 0)
	o.annotationMiddlewares = make(map[string]iris.Handler)
	o.middlewares = make([]iris.Handler, 0)
	o.parallelRunners = make([]ParallelRunner, 0)
	return o.initIris()
}

// InitIris
// prepare application fields for iris web framework.
func (o *Application) initIris() *Application {
	// Interrupt
	// listener register.
	iris.RegisterOnInterrupt(o.onInterrupt)

	// Execution rules
	// config for middlewares and main handler.
	//
	//   - false: i.Next() must be called at end of middleware.
	//   - true: do not call next.
	o.rule = iris.ExecutionRules{
		Begin: iris.ExecutionOptions{Force: false},
		Done:  iris.ExecutionOptions{Force: false},
		Main:  iris.ExecutionOptions{Force: true},
	}

	// Framework
	// bound for iris web framework.
	o.framework = iris.New()
	o.framework.SetExecutionRules(o.rule)
	return o
}

// LoadCrontabs
// prepare a process for crontab used to start with iris.
func (o *Application) loadCrontabs() {
	// Create
	// a crontab process.
	cron := crontab.New("crontab")

	// Range
	// registered crontab workers and add into cron process if validated.
	for _, x := range o.annotationCrontabs {
		if err := cron.Add(x); err != nil {
			log.Errorf(`[iris][crontab] invalid worker: error="%v"`, err)
			continue
		}
		log.Infof(`[iris][crontab] strategy="%v", handler="%s"`, x.GetStrategy(), x.GetClass())
	}

	// Add
	// a paralleling handler for crontab if necessary.
	if len(cron.GetWorkers()) > 0 {
		o.cron = cron

		// Add to
		// paralleling handlers list.
		o.AddParallels(func(ctx context.Context) {
			if err := o.cron.Start(ctx); err != nil {
				log.Errorf(`[iris][crontab] start failed: error="%v"`, err)
			}
		})
	}
}

// LoadIrisConfiguration
// prepare iris web framework configuration with yaml file. Load builtin
// if not accessed.
func (o *Application) loadIrisConfiguration() {
	// From config file.
	if f := config.Seek("iris.yml", "iris.yaml"); f.Found() {
		log.Infof(`[iris][configuration] from yaml file: path="%s"`, f.String())
		o.framework.Configure(iris.WithConfiguration(iris.YAML(f.String())))
		return
	}

	// With builtin.
	log.Infof(`[iris][configuration] with builtin`)
	o.framework.Configure(iris.WithConfiguration(iris.Configuration{
		LogLevel:                          "disable",
		DisableStartupLog:                 true,
		EnablePathIntelligence:            false,
		EnablePathEscape:                  true,
		ForceLowercaseRouting:             true,
		FireMethodNotAllowed:              true,
		DisableBodyConsumptionOnUnmarshal: true,
		TimeFormat:                        time.DateTime,
		Charset:                           "UTF-8",
	}))
}

func (o *Application) loadIrisDebugProf() {
	if conf.Config.DebugProf {
		p := pprof.New()
		s := strings.TrimSuffix(strings.TrimPrefix(conf.Config.DebugProfPrefix, "/"), "/")
		log.Infof(`[iris] enable debug pprof: path="/%s"`, s)
		o.framework.Any(fmt.Sprintf("/%s", s), p)
		o.framework.Any(fmt.Sprintf("/%s/{action:path}", s), p)
	}
}

// LoadIrisMiddlewares
// bind registered middlewares on iris.
func (o *Application) loadIrisMiddlewares() {
	// Error
	// middleware on all error handlers.
	o.framework.OnAnyErrorCode(middlewares.Error)

	// Global
	// middlewares ahead of user middlewares around all request handlers.
	o.framework.UseRouter(middlewares.Tracing, middlewares.Catch)

	// User
	// middlewares around all request handlers after builtin.
	if n := len(o.middlewares); n > 0 {
		o.framework.UseRouter(o.middlewares...)
		log.Infof(`[iris][middleware] bind global middlewares: count="%d"`, n)
	}
}

// LoadIrisHandlers
// load annotation handlers and bind on iris.
func (o *Application) loadIrisHandlers() {
	// Range
	// registered simple request handler on iris route.
	for _, v := range o.annotationHandlers {
		ls := make([]string, 0)
		ig := make([]string, 0)
		ms := make([]iris.Handler, 0)

		// Load
		// middlewares with annotation definitions.
		for _, k := range v.Middlewares {
			if fn, ok := o.annotationMiddlewares[k]; ok {
				ls = append(ls, k)
				ms = append(ms, fn)
			} else {
				ig = append(ig, k)
			}
		}

		// Bind.
		o.framework.Party(v.Path, ms...).Handle(v.Method, "", v.Handler)

		// Logger
		// handler action.
		cs := fmt.Sprintf(`[iris][handler] route="%s %s"`, v.Method, v.Path)

		if len(ls) > 0 {
			cs += fmt.Sprintf(`, middlewares=%v`, ls)
		}
		if len(ig) > 0 {
			cs += fmt.Sprintf(`, ignored-middlewares=%v`, ig)
		}
		log.Infof(`%s`, cs)
	}
}

// LoadIrisControllers
// load annotation controllers and bind on iris.
func (o *Application) loadIrisControllers() {
	var ref reflect.Type

	// Iterate added controllers.
	for _, v := range o.annotationControllers {
		// Controller
		// must be a pointer.
		if ref = reflect.TypeOf(v.Controller); ref.Kind() != reflect.Ptr {
			log.Errorf(`[iris][controller] controller must be a pointer: controller="%s"`, ref.String())
			continue
		}

		// Create mvc controller.
		func(entry *ControllerEntry, t reflect.Type) {
			// Prepare controller middlewares.
			ks := make([]string, 0)
			ms := make([]iris.Handler, 0)

			// Compare annotation middlewares with mapper.
			for _, k := range entry.Middlewares {
				if fn, ok := o.annotationMiddlewares[k]; ok {
					ks = append(ks, k)
					ms = append(ms, fn)
				} else {
					log.Errorf(`[iris][controller] undefined middleware: middleware="%s", controller="%s.%s"`, k, t.PkgPath(), t.Name())
				}
			}

			// Create api party.
			party := o.framework.Party(entry.Prefix, ms...)

			// Controller added on iris application.
			log.Infof(`[iris][controller] add controller: prefix="%s", middleware=%v, controller="%s.%s"`, entry.Prefix, ks, t.PkgPath(), t.Name())
			mvc.Configure(party, func(a *mvc.Application) {
				a.Handle(entry.Controller)
			})
		}(v, ref.Elem())
	}
}

func (o *Application) onInterrupt() {}

// Start
// starts the iris web framework as a service based on current application
// fields.
func (o *Application) start(ctx context.Context) {
	// Decrement
	// running statistic.
	defer atomic.AddInt32(&o.running, -1)

	// Increment
	// running statistic.
	if n := atomic.AddInt32(&o.running, 1); n > 1 {
		log.Errorf(`[iris] service started already`)
		return
	}

	// Context
	// create and logger.
	log.Infof(`[iris] service startup: name="%s", host="%s", port="%d", pid="%d"`, conf.Config.Name, conf.Config.Host, conf.Config.Port, conf.Config.Pid)
	o.ctx, o.cancel = context.WithCancel(ctx)

	// Load
	// dependent components and configuration.
	o.loadIrisConfiguration()
	o.loadCrontabs()
	o.loadIrisMiddlewares()
	o.loadIrisHandlers()
	o.loadIrisControllers()
	o.loadIrisDebugProf()

	// Called when done.
	defer func() {
		// Catch
		// start fatal and recover it.
		if r := recover(); r != nil {
			log.Fatalf(`[iris] service panic: fatal="%v"`, r)
		}

		// Cancel
		// context if necessary.
		if o.ctx.Err() == nil {
			o.cancel()
		}

		// Wait
		// paralleling handlers done.
		o.waitParallel()

		// Revert context.
		o.ctx, o.cancel = nil, nil
		log.Infof(`[iris] service closed`)
	}()

	// Start
	// iris application and paralleling handlers.
	if err := o.framework.Configure(func(_ *iris.Application) {
		o.startParallel()
	}).Run(iris.Addr(fmt.Sprintf(`%s:%d`, conf.Config.Host, conf.Config.Port))); err != nil {
		log.Errorf(`[iris] %v`, err)
	}
}

// StartParallel
// starts a paralleling runner list in goroutines.
func (o *Application) startParallel() {
	// Range
	// added paralleling runners.
	for _, x := range o.parallelRunners {
		atomic.AddInt32(&o.parallelRunning, 1)

		// Run
		// handler in a goroutine.
		go func(runner ParallelRunner) {
			// Called
			// when goroutine done.
			defer func() {
				if r := recover(); r != nil {
					log.Fatalf(`[iris][paralleling] runner panic: fatal="%v"`, r)
				}
				atomic.AddInt32(&o.parallelRunning, -1)
			}()

			// Run
			// paralleling handler.
			runner(o.ctx)
		}(x)
	}
}

// WaitParallel
// block 30 seconds until all paralleling runners are done.
func (o *Application) waitParallel() {
	// Return
	// if no paralleling runner.
	if len(o.parallelRunners) == 0 {
		return
	}

	// Prepare.
	var (
		duration = time.Second * 30
		now      = time.Now()
	)

	// Loop
	// until all paralleling runners are done.
	for {
		time.Sleep(time.Millisecond * 10)

		// Break
		// if all paralleling handlers done.
		if atomic.LoadInt32(&o.parallelRunning) == 0 {
			break
		}

		// Break
		// if blocked duration is greater than limit.
		if time.Now().Sub(now).Nanoseconds() >= duration.Nanoseconds() {
			break
		}
	}
}
